package com.raymond.cmpp.utils;

import java.util.*;

/**
 * CMPP消息id算法
 *
 * 信息标识，生成算法如下：
 * 采用64位（8字节）的整数：
 * （1）时间（格式为MMddHHmmss，即月日时分秒）：bit64~bit39，其中
 * bit64~bit61：月份的二进制表示；
 * bit60~bit56：日的二进制表示；
 * bit55~bit51：小时的二进制表示；
 * bit50~bit45：分的二进制表示；
 * bit44~bit39：秒的二进制表示；
 * （2）短信网关代码：bit38~bit17，把短信网关的代码转换为整数填写到该字段中；
 * （3）序列号：bit16~bit1，顺序增加，步长为1，循环使用。
 * 各部分如不能填满，左补零，右对齐。
 * （SP根据请求和应答消息的Sequence_Id一致性就可得到CMPP_Submit消息的Msg_Id）
 *
 * @author :  raymond
 * @version :  V1.0
 * @date :  2021-04-28 11:39
 */
public class CmppMsgId {

    private volatile long time;

    private long lastTime;

    private long gatewayCode;

    private long sequence;

    /**5位的工作机器id**/
    private int gatewayCodeBits = 10;
    /**每毫秒内产生的id数 2 的 12次方**/
    private int sequenceBits = 24;

    /**序号最大值**/
    private int maxSequence = ~(-1 << sequenceBits);
    /**这个是二进制运算，就是10 bit最多只能有1024个数字，也就是说工作机器id最多只能是1024以内**/
    @SuppressWarnings("FieldCanBeLocal")
    private int maxGatewayCode = ~(-1 << gatewayCodeBits);

    /**工作机器id需要左移位数**/
    private int gatewayCodeShift = sequenceBits;

    private static CmppMsgId cmppMsgId = null;

    public static CmppMsgId getInstance() {
        return getInstance(101);
    }

    public static CmppMsgId getInstance(int gatewayCode) {
        if (cmppMsgId == null) {
            synchronized (CmppMsgId.class) {
                if (cmppMsgId == null) {
                    cmppMsgId = new CmppMsgId(gatewayCode);
                }
            }
        }
        if (gatewayCode != cmppMsgId.gatewayCode) {
            throw new IllegalArgumentException(
                    String.format("worker Id Don't agree, The existing %d, The current %d", cmppMsgId.gatewayCode, gatewayCode));
        }
        return cmppMsgId;
    }

    public synchronized long nextId() {
        long now = System.currentTimeMillis() / 1000;
        if (now == this.time) {
            if (++sequence > maxSequence) {
                this.lastTime = tilNextMillis();
                sequence = 0;
            }
        } else {
            this.lastTime = tilNextMillis();
            sequence = 0;
        }
        return this.lastTime | (gatewayCode << gatewayCodeShift) | sequence;
    }

    public long pkNoMsgId(long msgId, long pkNo) {
        return msgId | (pkNo << 34);
    }

    private long tilNextMillis() {
        long timestamp = System.currentTimeMillis() / 1000;
        while (timestamp == this.time) {
            this.time = System.currentTimeMillis() / 1000;
        }
        this.time = System.currentTimeMillis() / 1000;
        return getTime(System.currentTimeMillis());
    }

    private long getTime(long time) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(time);
        long month = calendar.get(Calendar.MONTH) + 1;
        long day = calendar.get(Calendar.DAY_OF_MONTH);
        long hour = calendar.get(Calendar.HOUR_OF_DAY);
        long min = calendar.get(Calendar.MINUTE);
        long sec = calendar.get(Calendar.SECOND);
        return (month << 60) | (day << 55) | (hour << 50) | (min << 44) | (sec << 38);
    }

    private CmppMsgId(int gatewayCode) {
        // 检查机房id和工作机器id是否超过31 不能小于0
        if (gatewayCode > maxGatewayCode || gatewayCode < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0", gatewayCode));
        }
        this.gatewayCode = gatewayCode;
        this.time = System.currentTimeMillis() / 1000;
        this.lastTime = getTime(System.currentTimeMillis());
    }

    /**
     * 通过平台流水号获取流水号信息
     * @param ptMsgId 平台流水号
     * @return 流水号信息
     */
    public static String getYmd(long ptMsgId) {
        int year = Calendar.getInstance().get(Calendar.YEAR);
        int month = Calendar.getInstance().get(Calendar.MONTH) + 1;
        long mon = (ptMsgId >> 60) & 15;
        if (mon == 12 && month == 1) {
            year --;
        }
        long day = (ptMsgId >> 55) & 31;
        return year + "年" + String.format("%02d", mon) + "月" + String.format("%02d", day) +
                "日" + ((ptMsgId >> 50) & 63) + "时" + ((ptMsgId >> 44) & 63) + "分" + ((ptMsgId >> 38) & 63) + "秒" +
                ",第" + (((ptMsgId >> 34) & 15) + 1)+ "条" + ",网关编码:" + ((ptMsgId >> 24) & 1023) + ",序列号:" + (ptMsgId & 16777215);
    }

    public static void main(String[] args) throws InterruptedException {
        CmppMsgId cmppMsgId = CmppMsgId.getInstance(1);
//        Thread.sleep(2000);
        int count = 1 << 25;
        List<Long> list = new ArrayList<>();
        long start = System.currentTimeMillis();
        for (int i = 0; i < count; i++) {
            list.add(cmppMsgId.nextId());
        }
        System.out.println("耗时:" + (System.currentTimeMillis() - start));
        System.out.println("总数:" + list.size());
        Set<Long> set = new HashSet<>(list);
        System.out.println("去重总数:" + set.size());
        System.out.println(getYmd(2027443091681574913L));
        start = System.currentTimeMillis();
        System.out.println(Integer.parseInt(String.format("%tm", start)));
        System.out.println(Integer.parseInt(String.format("%td", start)));
        System.out.println(Integer.parseInt(String.format("%tH", start)));
        System.out.println(Integer.parseInt(String.format("%tM", start)));
        System.out.println(Integer.parseInt(String.format("%tS", start)));
    }
}
